#pragma once

#include <iostream>
#include <string>
#include <pthread.h>
#include <filesystem>
#include <fstream> //C++对文件的操作的两个头文件
#include <sstream>
#include <memory>
#include <unistd.h>
#include <time.h>
#include <cstdio>
#include "Mutex.hpp"

namespace LogModule
{
    #define FILE 2
    #define CONSOLE 1
    using namespace LockModule;

    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    const std::string defaultpath = "./log";
    const std::string defaultfile = "log.log";
    const std::string gsep = "\r\n";

    // 刷新策略: a、显示器打印 b、向指定文件写入
    class LogStrategy
    {
    public:
        LogStrategy() = default;

        virtual void SyncLog(std::string &message) = 0;
    };

    // 文件打印
    class FileStrategy : public LogStrategy
    {
    public:
        FileStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path), _file(file)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_path))
            {
                return;
            }
            // 创建目录
            std::filesystem::create_directories(_path);
        }

        void SyncLog(std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            //流插入到文件（文件路径+选项）
            std::ofstream out(filename, std::ios::app);
            if (!out.is_open())
            {
                return;
            }
            out << message << gsep;
            out.close();
        }

        ~FileStrategy()
        {
        }

    private:
        Mutex _mutex;
        const std::string &_path; // 日志文件所在路径
        const std::string &_file; // 日志文件本身
    };

    // 显示器打印日志
    class ConsoleStrategy : public LogStrategy
    {
    public:
        ConsoleStrategy() = default;

        void SyncLog(std::string &message) override
        {
            // 显示器也是文件，所以要加锁
            LockGuard lockguard(_mutex);
            std::cout << message << "\r\n";
        }
    private:
        Mutex _mutex;
    };

    // 形成日志等级-这种方式的枚举可以不用写enum，类似于class
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string level_to_string(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
        {
            return "DEBUG";
            break;
        }
        case LogLevel::INFO:
        {
            return "INFO";
            break;
        }
        case LogLevel::WARNING:
        {
            return "WARNING";
            break;
        }
        case LogLevel::ERROR:
        {
            return "ERROR";
            break;
        }
        case LogLevel::FATAL:
        {
            return "FATAL";
            break;
        }
        default:
            return "UNKNOWN";
        }
    }

    // 获取时间
    std::string GetTimeStamp()
    {
        time_t curr = time(nullptr);
        struct tm curr_tm;
        localtime_r(&curr, &curr_tm);
        char time_buffer[128];
        snprintf(time_buffer, 128, "%4d.%02d.%02d  %02d:%02d:%02d",
                 curr_tm.tm_year + 1900,
                 curr_tm.tm_mon + 1,
                 curr_tm.tm_mday,
                 curr_tm.tm_hour,
                 curr_tm.tm_min,
                 curr_tm.tm_sec);
        return time_buffer;
    }

    // 形成日志、按照不同的策略完成刷新
    class Logger
    {
    public:
        Logger(int signal = 1)
            : _signal(signal)
        {
        }

        class LogMessage
        {
        public:
            // 构建输出信息的左半部分
            LogMessage(LogLevel &level, const std::string &src_name, int line_number, Logger &logger, int signal = 1)
                : _curr_time(GetTimeStamp()), _level(level), _pid(getpid()), _src_name(src_name), _line_number(line_number), _logger(logger), _signal(signal)
            {
                std::stringstream ss;
                //需要写进去的都是同一类型，将其转换为字符串
                ss << "[" << _curr_time << "]"
                   << "[" << level_to_string(level) << "]"
                   << "[" << _pid << "]"
                   << "[" << _src_name << "]"
                   << "- ";
                _loginfo = ss.str(); // 将流写入的内容写入我们的字符串中
            }

            // 构建输出信息的右半部分
            template <class T>
            LogMessage &operator<<(const T &info) // 方便下一次进行输入
            {
                // 这里使用流插入进行操作的，但是这里不需要类似重载iostream返回流
                // 因为我们是写入我们的流插入
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }

            ~LogMessage()
            {
                if (_signal == 1)
                {
                    std::unique_ptr<LogStrategy> strategy = std::make_unique<ConsoleStrategy>();
                    strategy->SyncLog(_loginfo);
                }
                else
                {
                    std::unique_ptr<LogStrategy> strategy = std::make_unique<FileStrategy>();
                    strategy->SyncLog(_loginfo);
                }

                // if(_logger._fflush_strategy)
                // {
                //     _logger._fflush_strategy->SyncLog(_loginfo);
                // }
            }

        private:
            const std::string _curr_time;
            LogLevel _level;
            pid_t _pid;
            const std::string _src_name;
            int _line_number;
            std::string _loginfo; // 合并之后一条完整的信息
            Logger &_logger;
            int _signal;
        };

        LogMessage operator()(LogLevel level, const std::string &src_name = __FILE__,
                              int line_number = __LINE__)
        {
            // 这里我们选择返回临时对象，在其完成插入之前不会被释放，在被完成插入之后，会去调用析构，自动进行刷新
            return LogMessage(level, src_name, line_number, *this, _signal);
        }

        ~Logger()
        {
        }

    private:
        // std::unique_ptr<LogStrategy> _fflush_strategy;
        int _signal;
    };

// 全局日志对象——先完成左半部分在流插入右伴部分
}
